from unittest import TestCase

import opencog.atomspace
from opencog.atomspace import Atom, tvkey
from opencog.atomspace import types, is_a, get_type, get_type_name

from opencog.type_constructors import *
from opencog.type_ctors import get_thread_atomspace, set_thread_atomspace

from time import sleep

class AtomSpaceTest(TestCase):

    def setUp(self):
        self.space = AtomSpace()
        set_thread_atomspace(self.space)

    def tearDown(self):
        del self.space

    def test_bare(self):

        # Test dereferencing self as Value
        self.space
        self.space.type
        self.space.long_string()
        self.space.short_string()
        self.space.is_atom()
        self.space.is_node()
        self.space.is_link()
        list(self.space)
        str(self.space)
        len(self.space)

    def test_add_node(self):

        # Test long form atomspace node addition.

        # Test node add
        self.space.add_node(types.Node, "node" )

        # Test with not a proper truthvalue
        self.assertRaises(TypeError, self.space.add_node, types.Node, "test",
                0, True)
        # Test with bad type
        self.assertRaises(TypeError, self.space.add_node, "ConceptNode", "test",
                FloatValue([0.5, 0.8]))

        # From here on out we'll use the more compact type constructors
        a1 = Node("test")
        self.assertTrue(a1)
        # duplicates resolve to same atom
        a2 = Node("test")
        self.assertEqual(a1, a2)

        # Should fail when intentionally adding bad type
        caught = False
        try:
            self.space.add_node(types.Link, "test")
        except RuntimeError:
            caught = True
        self.assertEqual(caught, True)

        # Test adding with a truthvalue
        a3 = Node("test_w_tv")
        a3 = self.space.set_value(a3, tvkey, FloatValue([0.5, 0.8]))
        self.assertEqual(self.space.size(), 3)

    def test_add_link(self):
        n1 = Node("test1")
        n2 = Node("test2")
        l1 = Link(n1, n2)
        self.assertTrue(l1 is not None)
        l2 = Link(n1, n2)
        self.assertTrue(l2 is not None)
        self.assertTrue(l2 == l1)

        n3 = Node("test3")
        l3 = Link(n1, n3)
        self.assertTrue(l3 is not None)
        l3 = self.space.set_value(l3, tvkey, FloatValue([0.5, 0.8]))

        # Should fail when adding an intentionally bad type
        caught = False
        try:
            l1 = self.space.add_link(types.Node, [n1, n3])
        except RuntimeError:
            caught = True
        self.assertEqual(caught, True)

    def test_is_valid(self):
        a1 = Node("test1")
        # check with Atom object
        self.assertTrue(self.space.is_valid(a1))
        # check with bad type
        self.assertRaises(TypeError, self.space.is_valid, "test")

    def test_get_by_type(self):
        a1 = Node("test1")
        a2 = ConceptNode("test2")
        a3 = PredicateNode("test3")

        # test recursive subtypes
        result = self.space.get_atoms_by_type(types.Node)
        self.assertTrue(a1 in result)
        self.assertTrue(a2 in result)
        self.assertTrue(a3 in result)

        # links
        l1 = InheritanceLink(a1, a2)
        result = self.space.get_atoms_by_type(types.Link)
        self.assertTrue(l1 in result)

        # test non-recursive subtype
        result = self.space.get_atoms_by_type(types.Node, subtype=False)
        self.assertTrue(a1 in result)
        self.assertTrue(a2 not in result)
        self.assertTrue(a3 not in result)

        # test empty
        result = self.space.get_atoms_by_type(types.AnchorNode, subtype=False)
        self.assertEqual(len(result), 0)

    def test_incoming_by_type(self):
        a1 = Node("test1")
        a2 = ConceptNode("test2")
        a3 = PredicateNode("test3")

        # test no incoming Node for a1
        result = a1.incoming_by_type(types.Node)
        self.assertTrue(a1 not in result)

        # now check links
        l1 = InheritanceLink(a1, a2)
        result = a1.incoming_by_type(types.InheritanceLink)
        self.assertTrue(l1 in result)
        result = a2.incoming_by_type(types.InheritanceLink)
        self.assertTrue(l1 in result)
        result = a3.incoming_by_type(types.InheritanceLink)
        self.assertTrue(l1 not in result)

    def test_remove(self):
        a1 = Node("test1")
        a2 = ConceptNode("test2")
        a3 = PredicateNode("test3")

        self.assertTrue(a1 in self.space)
        self.assertTrue(a2 in self.space)
        self.assertTrue(a3 in self.space)

        self.space.remove(a1)
        self.assertTrue(a1 not in self.space)
        self.assertTrue(a2 in self.space)
        self.assertTrue(a3 in self.space)

        l = SimilarityLink(a2, a3)
        self.space.remove(a2, True) # won't remove it unless recursive is True
        self.assertTrue(a2 not in self.space)
        self.assertTrue(l not in self.space)

    def test_clear(self):
        a1 = Node("test1")
        a2 = ConceptNode("test2")
        a3 = PredicateNode("test3")
        self.space.clear()
        self.assertEqual(self.space.size(), 0)
        self.assertEqual(len(self.space), 0)

    def test_container_methods(self):
        self.assertEqual(len(self.space), 0)
        a1 = Node("test1")
        a2 = ConceptNode("test2")
        a3 = PredicateNode("test3")

        self.assertTrue(a1 in self.space)
        self.assertTrue(a2 in self.space)
        self.assertTrue(a3 in self.space)

        self.assertEqual(len(self.space), 3)

    def test_push_pop(self):
        a = ConceptNode('a')

        # Create temporary workspace
        current_as = push_thread_atomspace()
        self.assertFalse(get_thread_atomspace() == self.space)
        b = ConceptNode('b')
        # verify that 'b' is only in the current space.
        self.assertTrue(a in current_as)
        self.assertTrue(b in current_as)
        self.assertTrue(a in self.space)
        self.assertFalse(b in self.space)
        pop_thread_atomspace()

        self.assertTrue(get_thread_atomspace() == self.space)
        c = ConceptNode('c')
        # verify that current default atomspace is self.space
        self.assertTrue(c in self.space)
        self.assertTrue(a in self.space)
        self.assertFalse(b in self.space)


class AtomTest(TestCase):

    def setUp(self):
        self.space = AtomSpace()
        set_thread_atomspace(self.space)

    def tearDown(self):
        del self.space

    def test_create_child_atomspace(self):
        """
        Test that parent atomspace will not be deleted before child
        """
        a = opencog.atomspace.AtomSpace()
        b = opencog.atomspace.AtomSpace(a)
        del a

    def test_creation(self):
        a = Node("test1")
        self.assertEqual(a.name, "test1")
        self.assertEqual(a.get_value(tvkey), None)

    def test_w_truthvalue(self):
        a = Node("test2")
        tv = FloatValue([0.5, 100])
        a = self.space.set_value(a, tvkey, tv)
        self.assertEqual(a.get_value(tvkey), tv)

    def test_out(self):
        # test get out
        a1 = Node("test2")

        self.assertEqual(a1.out, [])

        a2 = Node("test3")
        tv = FloatValue([0.5, 100])
        a2 = self.space.set_value(a2, tvkey, tv)

        l = Link(a1, a2)
        self.assertEqual(l.out, [a1, a2])

        # ensure out is considered immutable
        self.assertRaises(AttributeError, setattr, l, "out", [a1])

    def test_arity(self):
        a1 = Node("test2")

        self.assertEqual(a1.arity, 0)

        a2 = Node("test3")
        tv = FloatValue([0.5, 100])
        a2 = self.space.set_value(a2, tvkey, tv)

        l = Link(a1, a2)
        self.assertEqual(l.arity, 2)

        # ensure arity is considered immutable
        self.assertRaises(AttributeError, setattr, l, "arity", 4)

    def test_type(self):
        # test get out
        a = Node("test2")
        a2 = Node("test3")
        l = Link(a, a2)

        # ensure type is considered immutable
        self.assertRaises(AttributeError, setattr, l, "type", 5)
        self.assertRaises(AttributeError, setattr, a, "type", 5)

        self.assertEqual(l.type_name, "Link")
        self.assertEqual(a.type_name, "Node")

    def test_create_child_atomspace(self):
        test = ConceptNode("test")
        b = AtomSpace(self.space)
        test2 = b.add_node(types.ConceptNode, 'test2')
        self.assertTrue(test in b.get_atoms_by_type(types.ConceptNode))
        self.assertTrue(test2 in b.get_atoms_by_type(types.ConceptNode))
        self.assertTrue(test2 not in self.space.get_atoms_by_type(types.ConceptNode))

    def test_strings(self):
        # set up a link and atoms
        a1 = Node("test1")
        a2 = Node("test2")
        l = Link(a1, a2)

        space_uuid = 0

        # test string representation
        a1_expected = "(Node \"test1\") ; [{0}]\n".format(space_uuid)
        a1_expected_long = \
            "(Node \"test1\") ; [{0}]\n"\
            .format(space_uuid)

        a2_expected = "(Node \"test2\") ; [{0}]\n".format(space_uuid)
        a2_expected_long = \
            "(Node \"test2\") ; [{0}]\n"\
            .format(space_uuid)

        l_expected = \
            "(Link\n  {0}  {1}) ; [{2}]\n"\
            .format(a1_expected, a2_expected, space_uuid)
        l_expected_long = \
            "(Link\n  {0}  {1}) ; [{2}]\n"\
            .format(a1_expected_long, a2_expected_long, space_uuid)

        # This just won't work as designed.
        #self.assertEqual(str(a1), a1_expected)
        #self.assertEqual(a1.long_string(), a1_expected_long)
        #self.assertEqual(str(a2), a2_expected)
        #self.assertEqual(a2.long_string(), a2_expected_long)
        #self.assertEqual(str(l), l_expected)
        #self.assertEqual(l.long_string(), l_expected_long)

class TypeTest(TestCase):

    def test_is_a(self):
        self.assertTrue(is_a(types.ConceptNode, types.Node))
        self.assertTrue(is_a(types.ConceptNode, types.Atom))

        self.assertTrue(is_a(types.ListLink, types.Link))
        self.assertTrue(is_a(types.ListLink, types.Atom))

        self.assertFalse(is_a(types.Link, types.Node))

    def test_get_type(self):
        self.assertEqual(get_type("ConceptNode"), types.ConceptNode)
        self.assertEqual(get_type(""), types.NO_TYPE)
        self.assertRaises(TypeError, get_type, 1)

    def test_get_type_name(self):
        self.assertEqual(get_type_name(types.Node), "Node")
        self.assertEqual(get_type_name(2231), "")
        # XXX FIXME is testing the name of the bottom type
        # a sane thing to do?
        self.assertEqual(get_type_name(types.NO_TYPE), "*** Bottom Type! ***")
